#!/usr/bin/env python

import optparse
import os
import urllib
import httplib
import threading
import Queue
try:
    from logging.log import Log
except:
    class TinyLog:
        def __printString(self, level, message):
            print '{level}: {message}'.format(level=level, message=message)
        def info(self, message):
            self.__printString('INFO', message)
        def fatal(self, message):
            self.__printString('FATAL', message)
        def debug(self, message):
            self.__printString('DEBUG', message)
        def error(self, message):
            self.__printString('ERROR', message)
    Log = TinyLog
log = Log()

class JSCompile(threading.Thread):
    class __BadTaskFormatError(Exception):
        message = 'Application config format error.'
        fatal = True
    class __SourceMissingError(Exception):
        message = 'Source from config is missing.'
        fatal = True
    class __NetworkError(Exception):
        message = 'Network problem occurred. Can\'t reach closure compiler.'
        fatal = True
    class __PackageNameError(Exception):
        message = 'Two ore more packages with the same name exists.'

    def __init__(self, taskQueue, group=None, target=None, name=None, args=(), kwargs=None, verbose=None):
        super(JSCompile, self).__init__(group, target, name, args, kwargs, verbose)
        self.taskQueue = taskQueue
        self.taskName = ''
        self.sourceFolder = ''

    def __processTask(self, task):
        package = task
        if not isinstance(package, dict):
            raise self.__BadTaskFormatError
        jsList = []
        packageFileList = package.get('jsList', [])
        for jsFile in packageFileList:
            try:
                f = open(os.path.join(self.sourceFolder, jsFile))
                jsList.append(('js_code', f.read()))
            except IOError:
                raise self.__SourceMissingError
        try:
            params = urllib.urlencode(jsList + [
                ('compilation_level', 'ADVANCED_OPTIMIZATIONS'),
                ('output_format', 'text'),
                ('output_info', 'compiled_code')
            ])
            headers = {'Content-type': 'application/x-www-form-urlencoded'}
            connection = httplib.HTTPConnection('closure-compiler.appspot.com')
            connection.request('POST', url='/compile', body=params, headers=headers)
            response = connection.getresponse()
            if os.path.isfile(os.path.join(self.sourceFolder, 'Release', package.get('packageName', ''))):
                raise self.__PackageNameError
            packageReleaseFile = open(os.path.join(self.sourceFolder, 'Release', package.get('packageName', '')), 'w')
            packageReleaseFile.write(response.read())
            log.info('Package: {0}. Compile is done.'.format(package.get('packageName'), ''))
        except self.__PackageNameError:
            raise self.__PackageNameError
        except:
            raise self.__NetworkError
        finally:
            if connection:
                connection.close()

    def run(self):
        while True:
            task = self.taskQueue.get()
            try:
                self.__processTask(task)
            except Exception as ex:
                if hasattr(ex, 'fatal'):
                    log.fatal(ex.message)
                else:
                    log.error(ex.message)
            finally:
                self.taskQueue.task_done()


def __printHeader(message, length=50, separator='='):
    header = 'Start: ' + message
    leftSeparatorsCount = int((length - len(header)) / 2)
    rightSeparatorsCount = length - len(header) - leftSeparatorsCount
    print '\n{0}| {1} |{2}\n'.format(
        separator * leftSeparatorsCount,
        header,
        separator * rightSeparatorsCount
    )

def __printFooter(message, length=50, separator='='):
    header = 'End: ' + message
    leftSeparatorsCount = int((length - len(header)) / 2)
    rightSeparatorsCount = length - len(header) - leftSeparatorsCount
    print '\n{0}| {1} |{2}\n'.format(
        separator * leftSeparatorsCount,
        header,
        separator * rightSeparatorsCount
    )

def main():
    argParser = optparse.OptionParser(description = 'jsbuild is a tool for compiling js files using mapping rules')
    argParser.add_option('-v', '--verbose',
                         action = 'store_true', dest = 'verbose', default = False,
                         help = 'noise level of logging')
    argParser.add_option('--app-config',
                         dest = 'appConfig', action = 'store', type = 'string',
                         metavar = 'FILE',
                         help = 'path to js mapping manifest in json format')
    

    (options, args) = argParser.parse_args()
    if not options.appConfig:
        argParser.error('--app-config option is required')

    try:
        import multiprocessing
        numOfCores = int(multiprocessing.cpu_count() / 2)
        if numOfCores == 0:
            numOfCores = 1
    except NotImplemented:
        numOfCores = 1

    __printHeader('JS Build')

    class CheckConfig():
        class __BadAppConfigError(Exception):
            message = 'Wrong application config (problem with path or content).'
            fatal = True
        class __UnknownError(Exception):
            message = 'Unknown error occurred.'
            fatal = True
        class __BadSourceFolder(Exception):
            message = 'JS source folder not found.'
            fatal = True

        def __enter__(self):
            try:
                appConfigFile = open(os.path.join(os.getcwd(), options.appConfig))
                import json
                data = json.loads(appConfigFile.read())
                if not isinstance(data, dict):
                    raise self.__BadAppConfigError
                appName = data.get('appName', None)
                jsSrcPath = data.get('resources', {}).get('js', {}).get('path', None)
                jsPackages = data.get('resources', {}).get('js', {}).get('packages', None)
                appRoot = os.path.dirname(os.path.join(os.getcwd(), options.appConfig))
                if not os.path.isdir(os.path.join(appRoot, jsSrcPath)):
                    raise self.__BadSourceFolder
                if os.path.isdir(os.path.join(appRoot, jsSrcPath, 'Release')):
                    for file in os.listdir(os.path.join(appRoot, jsSrcPath, 'Release')):
                        os.unlink(os.path.join(appRoot, jsSrcPath, 'Release', file))
                else:
                    os.makedirs(os.path.join(appRoot, jsSrcPath, 'Release'))
                return {
                    'appName': appName,
                    'jsSrcPath': jsSrcPath,
                    'jsPackages': jsPackages
                }
            except IOError:
                raise self.__BadAppConfigError
            except TypeError:
                raise self.__UnknownError
        def __exit__(self, type, value, traceback):
            try:
                self.appConfigFile.close()
            except:
                pass

    try:
        with CheckConfig() as jsMap:
            packagesList = jsMap.get('jsPackages', {})
            taskQueue = Queue.Queue()
            for package in packagesList:
                taskQueue.put(package)
            for _ in range(numOfCores):
                thread = JSCompile(taskQueue)
                thread.name = jsMap.get('appName', '')
                thread.sourceFolder = os.path.join(os.path.dirname(options.appConfig), jsMap.get('jsSrcPath', ''))
                thread.setDaemon(True)
                thread.start()
            taskQueue.join()
    except Exception as e:
        if hasattr(e, 'fatal'):
            log.fatal(e.message)
        else:
            log.error(e.message)

    __printFooter('JS Build')


if __name__ == '__main__':
    main()